

#include <esp_heap_caps.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>

#include <esp_log.h>
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_lcd_panel_ops.h"

#include <driver/ledc.h>
#include "driver/gpio.h"

#include "lvgl.h"

#include "esp_lcd_panel_st7796.h"

#include "lv_port_disp.h"

// ZX3D50CE02S-USRC-4832 Datasheet
// http://doc.smartpanle.com/_static/espboard/ESP32S3/ZX3D50CE02S-USRC-4832/ZX3D50CE02S-USRC-4832.pdf
// https://www.displayfuture.com/Display/datasheet/controller/ST7796s.pdf
// #define TAG  "lv_port_disp"

// 内存池
#if 0  // 在lv_mem.c 进行配置
#include "esp_heap_caps.h"
#define _LV_MEM_POOL_ALLOC_(x)      heap_caps_aligned_alloc(64, x, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT)
#define LV_MEM_POOL_ALLOC           _LV_MEM_POOL_ALLOC_
#endif
 
#define CONFIG_LCD_I80_COLOR_IN_PSRAM       1
   
#define CONFIG_LCD_I80_CONTROLLER_ST7789    1

#define CONFIG_LVGL_TICK_PERIOD_MS  CONFIG_LV_DISP_DEF_REFR_PERIOD

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// Please update the following configuration according to your LCD spec //////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if CONFIG_LCD_I80_COLOR_IN_PSRAM
// PCLK frequency can't go too high as the limitation of PSRAM bandwidth
#define CONFIG_LCD_PIXEL_CLOCK_HZ     (10 * 1000 * 1000)
#else
#define CONFIG_LCD_PIXEL_CLOCK_HZ     (10 * 1000 * 1000)
#endif // CONFIG_LCD_I80_COLOR_IN_PSRAM

#define CONFIG_LCD_BK_LIGHT_ON_LEVEL  1
#define CONFIG_LCD_BK_LIGHT_OFF_LEVEL !CONFIG_LCD_BK_LIGHT_ON_LEVEL

#define CONFIG_PIN_NUM_PCLK           47        // WR
#define CONFIG_PIN_NUM_CS             -1
#define CONFIG_PIN_NUM_DC             0
#define CONFIG_PIN_NUM_RST            4
#define CONFIG_PIN_NUM_BK_LIGHT       45
#define CONFIG_PIN_NUM_TE             48

#define CONFIG_PIN_NUM_DATA0          9
#define CONFIG_PIN_NUM_DATA1          46
#define CONFIG_PIN_NUM_DATA2          3
#define CONFIG_PIN_NUM_DATA3          8
#define CONFIG_PIN_NUM_DATA4          18
#define CONFIG_PIN_NUM_DATA5          17
#define CONFIG_PIN_NUM_DATA6          16
#define CONFIG_PIN_NUM_DATA7          15

// The pixel number in horizontal and vertical
#define CONFIG_LCD_H_RES              LCD_X_PIXELS
#define CONFIG_LCD_V_RES              LCD_Y_PIXELS
// Bit number used to represent command and parameter
#define CONFIG_LCD_CMD_BITS           8
#define CONFIG_LCD_PARAM_BITS         8
#define CONFIG_LCD_I80_BUS_WIDTH      8
 
// Supported alignment: 16, 32, 64. A higher alignment can enabkes higher burst transfer size, thus a higher i80 bus throughput.
#define CONFIG_PSRAM_DATA_ALIGNMENT   64

#define CONFIG_LVGL_BUFF_SIZE         (CONFIG_LCD_H_RES * CONFIG_LCD_V_RES / 2)    // 申请1/3屏幕内存

static esp_err_t lcd_bk_init(void);

static inline bool notify_lvgl_flush_ready(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx)
{
    lv_disp_drv_t *disp_driver = (lv_disp_drv_t *)user_ctx;
    lv_disp_flush_ready(disp_driver);
    return false;
}

static inline void lvgl_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map)
{
    esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t) drv->user_data;
    int offsetx1 = area->x1;
    int offsetx2 = area->x2;
    int offsety1 = area->y1;
    int offsety2 = area->y2;
    // copy a buffer's content to a specific area of the display
    esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, color_map);
}

static inline void lv_tick_handler(void *arg)
{
    /* Tell LVGL how many milliseconds has elapsed */
    lv_tick_inc(CONFIG_LVGL_TICK_PERIOD_MS);
}

 
void lv_port_disp_init(void)
{
    static lv_disp_draw_buf_t disp_buf; // contains internal graphic buffer(s) called draw buffer(s)
    static lv_disp_drv_t disp_drv;      // contains callback functions
 
    #ifdef TAG
    ESP_LOGI(TAG, "Initialize Intel 8080 bus");
    #endif
    esp_lcd_i80_bus_handle_t i80_bus = NULL;
    esp_lcd_i80_bus_config_t bus_config = {
        .clk_src = LCD_CLK_SRC_PLL160M,
        .dc_gpio_num = CONFIG_PIN_NUM_DC,
        .wr_gpio_num = CONFIG_PIN_NUM_PCLK,
        .data_gpio_nums = {
            CONFIG_PIN_NUM_DATA0,
            CONFIG_PIN_NUM_DATA1,
            CONFIG_PIN_NUM_DATA2,
            CONFIG_PIN_NUM_DATA3,
            CONFIG_PIN_NUM_DATA4,
            CONFIG_PIN_NUM_DATA5,
            CONFIG_PIN_NUM_DATA6,
            CONFIG_PIN_NUM_DATA7,
        },
        .bus_width = CONFIG_LCD_I80_BUS_WIDTH,
        .max_transfer_bytes = CONFIG_LVGL_BUFF_SIZE * sizeof(uint16_t),
        .psram_trans_align = CONFIG_PSRAM_DATA_ALIGNMENT,
        .sram_trans_align = 4,
    };
    ESP_ERROR_CHECK(esp_lcd_new_i80_bus(&bus_config, &i80_bus));

    esp_lcd_panel_io_handle_t io_handle = NULL;
    esp_lcd_panel_io_i80_config_t io_config = {
        .cs_gpio_num = CONFIG_PIN_NUM_CS,
        .pclk_hz = CONFIG_LCD_PIXEL_CLOCK_HZ,
        .trans_queue_depth = 10,
        .dc_levels = {
            .dc_idle_level = 0,
            .dc_cmd_level = 0,
            .dc_dummy_level = 0,
            .dc_data_level = 1,
        },
        .flags = {
            .swap_color_bytes = !LV_COLOR_16_SWAP, // Swap can be done in LvGL (default) or DMA
        },
        .on_color_trans_done = notify_lvgl_flush_ready,
        .user_ctx = &disp_drv,
        .lcd_cmd_bits = CONFIG_LCD_CMD_BITS,
        .lcd_param_bits = CONFIG_LCD_PARAM_BITS,
    };
    ESP_ERROR_CHECK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &io_handle));
 
    esp_lcd_panel_handle_t panel_handle = NULL;
    #ifdef TAG
    ESP_LOGI(TAG, "Install LCD driver of st7789");
    #endif
    esp_lcd_panel_dev_config_t panel_config = {
        .reset_gpio_num = CONFIG_PIN_NUM_RST,
        .flags.reset_active_high = 0,
        .color_space = ESP_LCD_COLOR_SPACE_BGR,
        .bits_per_pixel = 16,
    };

    ESP_ERROR_CHECK(esp_lcd_new_panel_st7796(io_handle, &panel_config, &panel_handle));

    esp_lcd_panel_reset(panel_handle);
    esp_lcd_panel_init(panel_handle);
    // Set inversion, x/y coordinate order, x/y mirror according to your LCD module spec
    // the gap is LCD panel specific, even panels with the same driver IC, can have different gap value
    esp_lcd_panel_invert_color(panel_handle, true);
    esp_lcd_panel_set_gap(panel_handle, 0, 0);

    esp_lcd_panel_mirror(panel_handle, CONFIG_LV_INVERT_X, CONFIG_LV_INVERT_Y);   
    esp_lcd_panel_swap_xy(panel_handle, CONFIG_LV_SWAPXY);

    // #if LCD_SHOW_0
    // esp_lcd_panel_mirror(panel_handle, true, false);   
    // esp_lcd_panel_swap_xy(panel_handle, false);
    // #if LCD_SHOW_90
    // esp_lcd_panel_mirror(panel_handle, false, false);   
    // esp_lcd_panel_swap_xy(panel_handle, true);
    // #elif LCD_SHOW_180
    // esp_lcd_panel_mirror(panel_handle, false, true); 
    // esp_lcd_panel_swap_xy(panel_handle, false);
    // #elif LCD_SHOW_270
    // esp_lcd_panel_mirror(panel_handle, true, true); 
    // esp_lcd_panel_swap_xy(panel_handle, true);
    // #endif

    // user can flush pre-defined pattern to the screen before we turn on the screen or backlight
    ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));

    #ifdef TAG
    ESP_LOGI(TAG, "Turn on LCD backlight");
    #endif
    ESP_ERROR_CHECK( lcd_bk_init() );
    lcd_disp_switch(io_handle, true);
 
    //===============================================================================================
    #ifdef TAG
    ESP_LOGI(TAG, "Initialize LVGL library...");
    #endif
    lv_init();

    // alloc draw buffers used by LVGL
    // it's recommended to choose the size of the draw buffer(s) to be at least 1/10 screen sized
    lv_color_t *buf1 = NULL;
    lv_color_t *buf2 = NULL;
#if CONFIG_LCD_I80_COLOR_IN_PSRAM
    buf1 = heap_caps_aligned_alloc(CONFIG_PSRAM_DATA_ALIGNMENT, CONFIG_LVGL_BUFF_SIZE * sizeof(lv_color_t), MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
    buf2 = heap_caps_aligned_alloc(CONFIG_PSRAM_DATA_ALIGNMENT, CONFIG_LVGL_BUFF_SIZE * sizeof(lv_color_t), MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
#else
    buf1 = heap_caps_malloc(CONFIG_LVGL_BUFF_SIZE * sizeof(lv_color_t), MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
    buf2 = heap_caps_malloc(CONFIG_LVGL_BUFF_SIZE * sizeof(lv_color_t), MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
#endif
    assert(buf1);
    assert(buf2);
    #ifdef TAG
    ESP_LOGI(TAG, "buf1@%p, buf2@%p", buf1, buf2);
    #endif
    // initialize LVGL draw buffers
    lv_disp_draw_buf_init(&disp_buf, buf1, buf2, CONFIG_LVGL_BUFF_SIZE);
    #ifdef TAG
    ESP_LOGI(TAG, "Register display driver to LVGL");
    #endif
    lv_disp_drv_init(&disp_drv);
    disp_drv.hor_res = CONFIG_LCD_H_RES;
    disp_drv.ver_res = CONFIG_LCD_V_RES;
    disp_drv.flush_cb = lvgl_flush_cb;
    disp_drv.draw_buf = &disp_buf;
    disp_drv.user_data = panel_handle;
    lv_disp_t *disp = lv_disp_drv_register(&disp_drv);
    assert(disp);

    /* 初始化默认显示配置 */
    lv_theme_t *th = lv_theme_default_init(disp, lv_palette_main(LV_PALETTE_BLUE), lv_palette_main(LV_PALETTE_RED), LV_THEME_DEFAULT_DARK + 1, &lv_font_montserrat_16);
    lv_disp_set_theme(disp, th);

    #ifdef TAG
    ESP_LOGI(TAG, "Install LVGL tick timer...");
    #endif
    // Tick interface for LVGL (using esp_timer to generate 2ms periodic event)
    const esp_timer_create_args_t lvgl_tick_timer_args = {
        .callback = &lv_tick_handler,
        .name = "lvgl_tick"
    };
    esp_timer_handle_t lvgl_tick_timer = NULL;
    ESP_ERROR_CHECK(esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer));
    ESP_ERROR_CHECK(esp_timer_start_periodic(lvgl_tick_timer,  CONFIG_LVGL_TICK_PERIOD_MS * 1000));  
 
    // while (1) {
    //     // raise the task priority of LVGL and/or reduce the handler period can improve the performance
    //     vTaskDelay(pdMS_TO_TICKS(10));
    //     // The task running lv_timer_handler should have lower priority than that running `lv_tick_inc`
    //     lv_timer_handler();
    // }
}
 
 
/**
 * @brief 初始化LCD背光PWM
 *
 * @return esp_err_t 错误代码
 */
static esp_err_t lcd_bk_init(void)
{
#define CONFIG_LEDC_SPEED_MODE          LEDC_LOW_SPEED_MODE    
#define CONFIG_LEDC_CHANNEL             LEDC_CHANNEL_0    
    /* 定时器配置 */
    ledc_timer_config_t lcd_bk_ledc_timer = {
        .duty_resolution = LEDC_TIMER_10_BIT, // LEDC驱动器占空比精度
        .freq_hz = 20000, // PWM频率； 20-14K,不然会导致模组共振有噪音
        .speed_mode = CONFIG_LEDC_SPEED_MODE,
        .timer_num = LEDC_TIMER_0, // ledc使用的定时器编号。若需要生成多个频率不同的PWM信号，则需要指定不同的定时器
        .clk_cfg = LEDC_AUTO_CLK, // 自动选择定时器的时钟源
    };

    /* ledc通道配置 */
    ledc_channel_config_t lcd_bk_ledc_channel = {
        .channel    = CONFIG_LEDC_CHANNEL, // LCD背光使用通道0
        .duty       = 0,    // 占空比0
        .gpio_num   = CONFIG_PIN_NUM_BK_LIGHT, // 连接背光的IO
        .speed_mode = CONFIG_LEDC_SPEED_MODE,
        .hpoint     = 0,
        .timer_sel  = LEDC_TIMER_0, // 使用上面初始化过的定时器
    };

   /* 初始化定时器0，将初始化好的定时器编号传给ledc通道初始化函数即可 */
   esp_err_t error = ledc_timer_config(&lcd_bk_ledc_timer);
   if (error) {
      return error;
   }

   /* 初始化ledc通道 */
   return ledc_channel_config(&lcd_bk_ledc_channel);
}

/**
 * @brief 设置背光亮度
 *
 * @param brightness 背光百分比，0-100
 * @return esp_err_t 错误代码
 */
esp_err_t lcd_bk_set_brightness(uint8_t brightness)
{
    /* 设定PWM占空比 */
    esp_err_t error = ledc_set_duty(CONFIG_LEDC_SPEED_MODE, CONFIG_LEDC_CHANNEL, brightness * 10);
    if (error) {
        return error;
    }

    /* 更新PWM占空比输出 */
    return ledc_update_duty(CONFIG_LEDC_SPEED_MODE, CONFIG_LEDC_CHANNEL);
}


void lcd_disp_switch(const esp_lcd_panel_io_handle_t io, bool sw)
{
    if (sw) {
        esp_lcd_panel_io_tx_param(io, LCD_CMD_DISPON, NULL, 0); //开显示
        // lcd_bk_set_brightness(CONFIG_DEFAULT_BACKLIGHT); // 打开背光 CONFIG_DEFAULT_BACKLIGHT
        #ifdef TAG
        ESP_LOGI(TAG, "lcd display on: 100");
        #endif
    } else {
        esp_lcd_panel_io_tx_param(io, LCD_CMD_DISPOFF, NULL, 0); //关显示
        lcd_bk_set_brightness(0);  // 关闭背光
        #ifdef TAG
        ESP_LOGI(TAG, "lcd display off");
        #endif
    }
}

void lcd_disp_onoff(bool sw)
{
    // lcd_disp_switch(io_handle, sw);
}